#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2025/1/11
# @Author : Joker
# @Version : Python 3.7
# @Software: PyCharm
import copy
import json
import re
import jsonpath
from dataclasses import dataclass
from utils.yaml import yamlUtil
from utils.function import funcUtil
from utils.db import dbUtil
from utils.log import logger


@dataclass
class CaseData:
    feature: str
    story: str
    title: str
    request: dict
    step: str = None
    extract: dict = None
    validate: dict = None


class DataUtil:

    @staticmethod
    def validate_data(case):

        def validate_dict_data(case_data):
            try:
                validated_data = CaseData(**case_data)
            except Exception as e:
                logger.error(f"yaml文件缺少必要参数:{e}")
                raise Exception("yaml文件缺少必要参数")
            else:
                return validated_data

        if isinstance(case, dict):
            return validate_dict_data(case)
        if isinstance(case, list):
            return [validate_dict_data(_) for _ in case]

    @staticmethod
    def _convert_response(response):
        """把response中的json方法转为属性"""
        response_copy = copy.deepcopy(response)
        if 'json' in response.headers.get('Content-Type'):
            response_copy.json = response.json()
        else:
            response_copy.json = {}
        return response_copy

    def extract_data(self, response, var, attr, expr: str, index=0):
        response_copy = self._convert_response(response)
        data = getattr(response_copy, attr)
        if expr.startswith("$."):
            extract_list = jsonpath.jsonpath(data, expr)
        else:
            extract_list = re.findall(expr, data)
        extract_value = extract_list[index] if extract_list else None
        yamlUtil.write_to_yaml({var: extract_value})
        logger.info(f"保存变量 {var}: {extract_value}")

    @staticmethod
    def replace_data(request_data: dict):

        def replace(data: str):
            expr = "\\$\\{(.*?)\\((.*?)\\)\\}"  # ${func(*args)} 匹配出 func 和 *args
            func_list = re.findall(expr, data)
            for func in func_list:
                func_name, args = func
                if not args:
                    result = getattr(funcUtil, func_name)()
                else:
                    result = getattr(funcUtil, func_name)(*args.split(','))

                original = '${' + func_name + '(' + args + ')}'
                data = data.replace(original, result)
            return data

        data_str = json.dumps(request_data)
        replaced_data = replace(data_str)
        data_dict = json.loads(replaced_data)

        return data_dict

    def assert_response(self, response, assert_type, assert_data: dict):
        logger.debug("用例开始进行断言:")
        response_copy = self._convert_response(response)
        for msg, data in assert_data.items():
            expect, actual_source = data
            # actual_source可能是str(response的属性)或者是list([属性, 取值表达式]) 或者是sql
            if isinstance(actual_source, list):
                attr, expr = actual_source
                logger.info(f"断言从response属性中取值, attr:{attr}, expr:{expr}")
                data = getattr(response_copy, attr)
                if expr.startswith("$."):
                    result = jsonpath.jsonpath(data, expr)
                else:
                    result = re.findall(expr, data)
                actual = result[0] if result else None
            else:
                attr_type = ['text', 'json', 'headers', 'cookies', 'reason', 'status_code']
                if actual_source in attr_type:
                    logger.info(f"断言使用response属性:{actual_source}")
                    actual = getattr(response_copy, actual_source)
                else:
                    logger.info(f"断言使用sql:{actual_source}")
                    execute_result = dbUtil.execute(actual_source)
                    actual = execute_result[0] if execute_result else None

            logger.info(f'断言类型：{assert_type}, 预期结果：{expect}, 实际结果：{actual}')

            if assert_type in ['equals', 'db_equals']:
                assert expect == actual, msg
            if assert_type in ['contains', 'db_contains']:
                assert expect in actual, msg


dataUtil = DataUtil()
